package edu.toronto.whimper.MapObjects;

import java.util.ArrayList;

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;

import edu.toronto.whimper.NoiseMap;
import edu.toronto.whimper.GeneralObjects.NoiseSample;

public class MapOverlay extends Overlay {

	private int mapSmallRegionBlocksSize = 40;
	private GeoPoint lastMapDrawLocation1 = new GeoPoint(1, 1);
	private GeoPoint lastMapDrawLocation2 = new GeoPoint(1, 1);
	private int estimatedNoiseMapWidth = 320, estimatedNoiseMapHeight = 480;
	private Bitmap estimatedNoiseOverlayBitmap = Bitmap.createBitmap(estimatedNoiseMapWidth*3, estimatedNoiseMapHeight*3, Bitmap.Config.ARGB_8888);
	private int lastMapDrawLevel = -1;
	private int colorTransparencyLevel = 128;
	private int noisePointerOnMapCircleRadius = 5;
	private ArrayList<NoiseSample> noiseSamples;
	private int maximumEffectRadiusOfNoiseSamples = 1000;
	private volatile UpdateNoiseMapThread calculateNoiseMapThread = null;

	public void setNoiseSamples(ArrayList<NoiseSample> newNoiseSamples)
	{
		noiseSamples = newNoiseSamples;
		lastMapDrawLocation2 = new GeoPoint(1, 1);
		//mapView.invalidate();
	}
	
	public void draw(Canvas canvas, MapView mapv, boolean shadow)
	{
		Projection mapProjection = mapv.getProjection();
        
        Point generatedNoiseMapStartPoint = new Point();
        Point lastMapDrawPoint2 = new Point();
        
        int currentMapDrawDistance;
        
        mapProjection.toPixels(lastMapDrawLocation1, generatedNoiseMapStartPoint);
        mapProjection.toPixels(lastMapDrawLocation2, lastMapDrawPoint2);
        
        currentMapDrawDistance = lastMapDrawPoint2.x - generatedNoiseMapStartPoint.x;

        if (( currentMapDrawDistance != estimatedNoiseMapWidth) ||
        		(generatedNoiseMapStartPoint.x > 0) ||
        		(generatedNoiseMapStartPoint.y > 0) ||
        		(generatedNoiseMapStartPoint.x + 2 * estimatedNoiseMapWidth < 0) ||
        		(generatedNoiseMapStartPoint.y + 2 * estimatedNoiseMapHeight < 0))
        {
        	lastMapDrawLocation1 = mapProjection.fromPixels(-estimatedNoiseMapWidth, -estimatedNoiseMapHeight);
        	lastMapDrawLocation2 = mapProjection.fromPixels(0, 0);

        	if ((calculateNoiseMapThread != null) && calculateNoiseMapThread.isAlive())
            {
        		calculateNoiseMapThread.stopCalculating();
            	calculateNoiseMapThread.interrupt();
            	calculateNoiseMapThread = null;
            }
            
            calculateNoiseMapThread = new  UpdateNoiseMapThread(mapProjection, mapv);
            
            calculateNoiseMapThread.start();

            
        }
        
        super.draw(canvas, mapv, shadow);
        
        mapProjection.toPixels(lastMapDrawLocation1, generatedNoiseMapStartPoint);
        
        synchronized(estimatedNoiseOverlayBitmap)
        {
        	canvas.drawBitmap(estimatedNoiseOverlayBitmap, generatedNoiseMapStartPoint.x, generatedNoiseMapStartPoint.y, null);
        }


        
    }
	
	private float estimateNoiseLevel(GeoPoint location)
	{
		float estimatedNoise, weightSum;
		float [] distanceResult = new float[1];
		
		weightSum = 0;
		estimatedNoise = 0;
		for (NoiseSample noiseSample : noiseSamples) 
		{
			android.location.Location.distanceBetween(location.getLatitudeE6()/1000000.0, location.getLongitudeE6()/1000000.0, 
					noiseSample.getLocation().getLatitude(), noiseSample.getLocation().getLongitude(), distanceResult);
			if (distanceResult[0] < maximumEffectRadiusOfNoiseSamples)
			{
				//weightSum += maximumEffectRadiusOfNoiseSamples-distanceResult[0];
				//estimatedNoise += (maximumEffectRadiusOfNoiseSamples-distanceResult[0]) * noiseSample.getNoise();
				weightSum += maximumEffectRadiusOfNoiseSamples/distanceResult[0];
				estimatedNoise += (maximumEffectRadiusOfNoiseSamples/distanceResult[0]) * noiseSample.getNoise();
			}
		}
		
		if (weightSum > 0)
		{
			estimatedNoise /= weightSum;
		}
		
		return estimatedNoise;
	}
	
	private float distanceBetweenGeoPoints(GeoPoint firstPoint, GeoPoint secondPoint)
	{
		float [] distanceResult = new float[2];
		
		android.location.Location.distanceBetween(firstPoint.getLatitudeE6()/1000000.0, firstPoint.getLongitudeE6()/1000000.0, 
				secondPoint.getLatitudeE6()/1000000.0, secondPoint.getLongitudeE6()/1000000.0, distanceResult);
		
		return distanceResult[0];
	}
	

	private int convertNoiseToColor(float inputNoiseValue, int transparencyValue)
	{
		// Noise color mapping method:
		// <10 db are considered unknown values and set as Gray (200,200,200)
		// 35 db is set as Cyan (25,225,225)
		// 35-55 db are maped to Cyan-Green range
		// 55 db is set as Green (25,225,25)
		// 55-75 db are mapped to Green-Amber range
		// 75 db is set as Amber (225,225,25)
		// 75-95 db are mapped to Amber-Red range
		// 95 db is set as Red (225,25,25)
		
		if (inputNoiseValue < 10)
		{
			return Color.argb(transparencyValue, 200, 200, 200);
		}
		
		float noiseValue = inputNoiseValue;
		
		// Be sure that it is in the range of 35-96
		if (noiseValue < 35)
		{
			noiseValue = 35;
		}
		if (noiseValue > 95)
		{
			noiseValue = 95;
		}
		
		if (noiseValue < 55)
		{
			// The 35-55 range
			return Color.argb(transparencyValue, 25, 225, (int) (225 - 10 * (noiseValue-35)));
		}
		if (noiseValue < 75)
		{
			// The 55-75 range
			return Color.argb(transparencyValue, (int) (25 + 10 * (noiseValue-55)), 225, 25);
		}
		// The 75-95 range
		return Color.argb(transparencyValue, 225, (int) (225 - 10 * (noiseValue-75)), 25);
	}

	
	private class UpdateNoiseMapThread extends Thread {	

		private Projection mapProjection;
		private MapView mapView;
		private boolean stopWork = false;
		
		public UpdateNoiseMapThread(Projection inputMapProjection, MapView inputMapView)
		{
			mapProjection = inputMapProjection;
			mapView = inputMapView;
		}
		
		public void stopCalculating(){
			stopWork = true;
		}
        
        @Override
        public void run() {
        	
        	Paint myPaint = new Paint();
            GeoPoint currentBlockLocation;
            
            
            synchronized(estimatedNoiseOverlayBitmap)
            {
            	//estimatedNoiseOverlayBitmap = Bitmap.createBitmap(estimatedNoiseMapWidth*3, estimatedNoiseMapHeight*3, Bitmap.Config.ARGB_8888);
            	estimatedNoiseOverlayBitmap.eraseColor(0);
            }
            
            Canvas bitmapCanvas;
            synchronized(estimatedNoiseOverlayBitmap)
            {
            	bitmapCanvas = new Canvas(estimatedNoiseOverlayBitmap);
            }
            
            Point currentNoisePoint = new Point();
            
    		for (NoiseSample noiseSample : noiseSamples) 
        	{
    			if (stopWork)
    			{
    				return;
    			}

        		int latE6 =  (int)(noiseSample.getLocation().getLatitude()* 1E6);
        		int longE6 = (int)(noiseSample.getLocation().getLongitude()* 1E6);
        		float noisedb = (float)noiseSample.getNoise();

        		GeoPoint noiseGeoPoint = new GeoPoint(latE6, longE6);

        		mapProjection.toPixels(noiseGeoPoint, currentNoisePoint);
        		
        		currentNoisePoint.x += estimatedNoiseMapWidth;
        		currentNoisePoint.y += estimatedNoiseMapHeight;

        		if ( (currentNoisePoint.x > 0) && (currentNoisePoint.y > 0) && 
        				(currentNoisePoint.x < estimatedNoiseMapWidth*3) && (currentNoisePoint.y < estimatedNoiseMapHeight*3) )
        		{
        	        synchronized(estimatedNoiseOverlayBitmap)
        	        {
    	    			myPaint.setColor(Color.BLACK);
    	    			bitmapCanvas.drawCircle(currentNoisePoint.x, currentNoisePoint.y, noisePointerOnMapCircleRadius+1, myPaint);
    	    			myPaint.setColor(convertNoiseToColor(noisedb, 255));
    	    			bitmapCanvas.drawCircle(currentNoisePoint.x, currentNoisePoint.y, noisePointerOnMapCircleRadius, myPaint);
        	        }
        		}
        	}
    		
    		mapView.postInvalidate();

            float estimatedNoise;
            
            myPaint.setARGB(128, 128, 128, 128);

            for (int i = estimatedNoiseMapWidth ; i < estimatedNoiseMapWidth*2 ; i+=mapSmallRegionBlocksSize)
            {
            	for (int j = estimatedNoiseMapHeight ; j <  estimatedNoiseMapHeight*2 ; j+=mapSmallRegionBlocksSize)
            	{
        			if (stopWork)
        			{
        				return;
        			}

            		currentBlockLocation = mapProjection.fromPixels(i+mapSmallRegionBlocksSize/2 - estimatedNoiseMapWidth, j+mapSmallRegionBlocksSize/2 - estimatedNoiseMapHeight);
            		estimatedNoise = estimateNoiseLevel(currentBlockLocation);
            		
            		myPaint.setColor(convertNoiseToColor(estimatedNoise, colorTransparencyLevel));
            		synchronized(estimatedNoiseOverlayBitmap)
                    {
                    	bitmapCanvas.drawRect(i, j, i+mapSmallRegionBlocksSize, j+mapSmallRegionBlocksSize, myPaint);
                    }
                	mapView.postInvalidate();
            	}
            }

            for (int i = 0 ; i < estimatedNoiseOverlayBitmap.getWidth() ; i+=mapSmallRegionBlocksSize)
            {
            	for (int j = 0 ; j <  estimatedNoiseOverlayBitmap.getHeight() ; j+=mapSmallRegionBlocksSize)
            	{
        			if (stopWork)
        			{
        				return;
        			}

            		if ( (i >= estimatedNoiseMapWidth) && (i < 2*estimatedNoiseMapWidth) && (j >= estimatedNoiseMapHeight) && (j < 2*estimatedNoiseMapHeight))
            		{
            			continue;
            		}
            		currentBlockLocation = mapProjection.fromPixels(i+mapSmallRegionBlocksSize/2 - estimatedNoiseMapWidth, j+mapSmallRegionBlocksSize/2 - estimatedNoiseMapHeight);
            		estimatedNoise = estimateNoiseLevel(currentBlockLocation);
            		
            		myPaint.setColor(convertNoiseToColor(estimatedNoise, colorTransparencyLevel));
            		synchronized(estimatedNoiseOverlayBitmap)
                    {
                    	bitmapCanvas.drawRect(i, j, i+mapSmallRegionBlocksSize, j+mapSmallRegionBlocksSize, myPaint);
                    }
            	}
            	mapView.postInvalidate();
            }

            for (NoiseSample noiseSample : noiseSamples) 
        	{
    			if (stopWork)
    			{
    				return;
    			}


        		int latE6 =  (int)(noiseSample.getLocation().getLatitude()* 1E6);
        		int longE6 = (int)(noiseSample.getLocation().getLongitude()* 1E6);
        		float noisedb = (float)noiseSample.getNoise();

        		GeoPoint noiseGeoPoint = new GeoPoint(latE6, longE6);

        		mapProjection.toPixels(noiseGeoPoint, currentNoisePoint);
        		
        		currentNoisePoint.x += estimatedNoiseMapWidth;
        		currentNoisePoint.y += estimatedNoiseMapHeight;

        		if ( (currentNoisePoint.x > 0) && (currentNoisePoint.y > 0) && 
        				(currentNoisePoint.x < estimatedNoiseMapWidth*3) && (currentNoisePoint.y < estimatedNoiseMapHeight*3) )
        		{
        			synchronized(estimatedNoiseOverlayBitmap)
        	        {
        	        	myPaint.setColor(Color.BLACK);
        	        	bitmapCanvas.drawCircle(currentNoisePoint.x, currentNoisePoint.y, noisePointerOnMapCircleRadius+1, myPaint);
        				myPaint.setColor(convertNoiseToColor(noisedb, 255));
        				bitmapCanvas.drawCircle(currentNoisePoint.x, currentNoisePoint.y, noisePointerOnMapCircleRadius, myPaint);
        	        }
        		}
        	}
    		
        	mapView.postInvalidate();
        }
        
    }
}
